package com.scientific.research.gobang.service.codegame.impl;

import com.scientific.research.gobang.Utils.RedisUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.scientific.research.gobang.mapper.RankingMapper;
import com.scientific.research.gobang.pojo.RankUser;
import com.scientific.research.gobang.pojo.SubmitList;
import com.scientific.research.gobang.pojo.SystemMessage;
import com.scientific.research.gobang.pojo.User;
import com.scientific.research.gobang.mapper.SubmitListMapper;
import com.scientific.research.gobang.mapper.SystemMapper;
import com.scientific.research.gobang.mapper.UserMapper;
import com.scientific.research.gobang.service.CodeAndCodeService;
import com.scientific.research.gobang.utils.FileUtils;
import org.apache.ibatis.annotations.Update;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;

@Service("codeAndCodeServiceImpl")
public class CodeAndCodeServiceImpl implements CodeAndCodeService {

    @Autowired
    private SubmitListMapper submitListMapper;
    @Autowired
    public RunCodeServiceImpl userOne;
    @Autowired
    public RunCodeServiceImpl userTwo;
    @Autowired
    public SystemMapper systemMapper;
    @Autowired
    private UserMapper userMapper;
    @Resource
    private RedisUtil redisUtil;
    @Autowired
    private RankingMapper rankingMapper;

    private String runTime;//记录用户运行代码时的时间

    @Override
    public void noCompileInit(String userTwoEmail)//不用编译的人机初始化方法
    {
        QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
        wrapper.eq("key_message", "robot");
        String name = systemMapper.selectOne(wrapper).getValueMessage();
        Map<String, Object> selectName = new HashMap<>();
        selectName.put("name", name);
        User user = userMapper.selectByMap(selectName).get(0);
        name += "and" + userTwoEmail;
        String language = user.getCodeLanguage();
        userOne.setUserName(name.replace(".", "-"));
        userOne.setLanguage(language);
    }

    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public void ClearRobotInputFile()//人机对弈时清理输入文件
    {
        userOne.clearInputFile();
    }

    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public String ManAndCodeInit(String userTwoEmail)//人机对弈初始化函数,就是编译机器代码
    {
        QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
        wrapper.eq("key_message", "robot");
        String name = systemMapper.selectOne(wrapper).getValueMessage();
        Map<String, Object> selectName = new HashMap<>();
        selectName.put("name", name);
        User user = userMapper.selectByMap(selectName).get(0);
        name += "and" + userTwoEmail;
        String language = user.getCodeLanguage();
        String code = user.getCode();
        wrapper = new QueryWrapper<>();
        wrapper.eq("key_message", "mainCodeJava");
        String mainCode = systemMapper.selectOne(wrapper).getValueMessage();
        int tag = mainCode.indexOf("public class java") + 18;
        String between = mainCode.substring(0, tag);
        code = between + "\n" + code + "\n" + mainCode.substring(tag);

        //初始化变量1运行环境
        userOne.thisInit(name, language);

        if ("java".equals(language)) {
            language = "java.java";
        } else if ("python".equals(language)) {
            language = "python.py";
        } else if ("c++".equals(language) || "c".equals(language)) {
            language = "c++.cpp";
        }
        between = userOne.appendPath(RunCodeServiceImpl.basePath, name.replace(".", "-"));
        between = userOne.appendPath(between, language);
        FileUtils.writer(between, code, false);
        String result = userOne.setAndCompile(name, language.substring(0, language.indexOf(".")));
        if (result.length() != 0) {
            return "fail";//编译出错
        } else {
            return "success";
        }
    }

    //每一次落子都会调用ManAndCode方法，负责判断有没有成局，如果赢了或者输了就返回结果；如果没成局就将棋盘信息写入文件
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public String ManAndCode(String manPosition) {
        Map result;
        userOne.setInput(manPosition);
        userOne.setIsMeBlack(1);
        if (userOne.check() == 1) {
            return "win";
        }
        result = userOne.doOnceRun();
        if (result.get("value") == null) {
            return "error";
        }
        userOne.setIsMeBlack(-1);
        if (userOne.check() == 1) {
            return "lose";
        }
        return (String) result.get("value");
    }

    //执行一次双方的代码对弈，包括用户1黑vs用户2白、用户1白vs用户2黑
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public void CodeAndCodeOnce(String OneName, String OneLanguage, String userOneCode, String TwoName, String TwoLanguage, String userTwoCode) {
        //代码执行过程中的错误信息
        String errorMessage = "";

        //初始化用户1和用户2
        userOne.thisInit(OneName, OneLanguage);
        userTwo.thisInit(TwoName, TwoLanguage);

        //拼接用户的代码并存储至相应目录
        String userOneMainCode = "", userTwoMainCode = "";
        String userOneFileName = "", userTwoFileName = "";
        int userOneTag = 0, userTwoTag = 0;
        if ("java".equals(OneLanguage)) {
            QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
            wrapper.eq("key_message", "mainCodeJava");
            userOneMainCode = systemMapper.selectOne(wrapper).getValueMessage();
            userOneTag = userOneMainCode.indexOf("public class java") + 18;
            userOneFileName = "java.java";
        } else if ("python".equals(OneLanguage)) {
            QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
            wrapper.eq("key_message", "mainCodePython");
            userOneMainCode = systemMapper.selectOne(wrapper).getValueMessage();
            userOneTag = 3;//待测量
            userOneFileName = "python.py";
        } else if ("c++".equals(OneLanguage) || "c".equals(OneLanguage)) {
            QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
            wrapper.eq("key_message", "mainCodeC++");
            userOneMainCode = systemMapper.selectOne(wrapper).getValueMessage();
            userOneTag = userOneMainCode.indexOf("int** map = new int*[15];") + 26;
            userOneFileName = "c++.cpp";
        }

        if ("java".equals(TwoLanguage)) {
            QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
            wrapper.eq("key_message", "mainCodeJava");
            userTwoMainCode = systemMapper.selectOne(wrapper).getValueMessage();
            userTwoTag = userTwoMainCode.indexOf("public class java") + 18;
            userTwoFileName = "java.java";
        } else if ("python".equals(TwoLanguage)) {
            QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
            wrapper.eq("key_message", "mainCodePython");
            userTwoMainCode = systemMapper.selectOne(wrapper).getValueMessage();
            userTwoTag = 3;//待测量
            userTwoFileName = "python.py";
        } else if ("c++".equals(TwoLanguage) || "c".equals(OneLanguage)) {
            QueryWrapper<SystemMessage> wrapper = new QueryWrapper<>();
            wrapper.eq("key_message", "mainCodeC++");
            userTwoMainCode = systemMapper.selectOne(wrapper).getValueMessage();
            userTwoTag = userTwoMainCode.indexOf("int** map = new int*[15];") + 26;
            userTwoFileName = "c++.cpp";
        }
        String between = userOneMainCode.substring(0, userOneTag);
        String OneCode = userOneCode; //OneCode是用户提交代码，userOneCode是被拼接后的代码
        userOneCode = between + "\n" + userOneCode + "\n" + userOneMainCode.substring(userOneTag);
        between = userTwoMainCode.substring(0, userTwoTag);
        userTwoCode = between + "\n" + userTwoCode + "\n" + userTwoMainCode.substring(userTwoTag);
        between = userOne.appendPath(RunCodeServiceImpl.basePath, OneName.replace(".", "-"));
        between = userOne.appendPath(between, userOneFileName);
        FileUtils.writer(between, userOneCode, false);
        between = userOne.appendPath(RunCodeServiceImpl.basePath, TwoName.replace(".", "-"));
        between = userOne.appendPath(between, userTwoFileName);
        FileUtils.writer(between, userTwoCode, false);


        //setAndCompile编译双方玩家的代码
        String userOneResult = userOne.setAndCompile(OneName, OneLanguage);
        String userTwoResult = userTwo.setAndCompile(TwoName, TwoLanguage);
        if (userOneResult.length() != 0) {
            errorMessage = OneName + "#" + userOneResult;
        }
        if (userTwoResult.length() != 0) {
            errorMessage = TwoName + "#" + userTwoResult;
        }
        //如果编译没有出错，就开始运行代码。
        // 运行顺序是：BlackFirstRun(循环调用用户1，用户2的代码，运行20次blackFirstRunTool)
        // =>blackFirstRunTool(调用doOnceRun运行一次代码并存入该步耗时、超时和错误信息)
        // =>doOnceRun(获取用户选择的语言和运行的cmd信息，交给runProcess执行代码)
        // =>runProcess(最底层的内核代码，调用代码执行，识别运行错误，内存超限，使用异步多线程Future判断超时)
        // 最后 调用JurgeAndSaveSubmitList保存结果
        // 再调用saveRankUser_And_UserCode更新用户排名信息、用户代码信息（编译错误不会执行该函数、且平局不会更新排名）
        if ("".equals(errorMessage)) {
            String result;
            //将用户执黑棋的结果存入SubmitList
            result = BlackFirstRun(0);
            JurgeAndSaveSubmitList(result, OneCode, "black");
            saveRankUser_And_UserCode(OneName, OneCode, result);
            String path = userOne.appendPath(RunCodeServiceImpl.basePath, OneName.replace(".", "-"));
            path = userOne.appendPath(path, "input.txt");
            FileUtils.writer(path, "", false);
            path = userOne.appendPath(RunCodeServiceImpl.basePath, TwoName.replace(".", "-"));
            path = userOne.appendPath(path, "input.txt");
            FileUtils.writer(path, "", false);
            //将用户执白棋的结果存入SubmitList
            result = BlackFirstRun(1);
            JurgeAndSaveSubmitList(result, OneCode, "white");
            saveRankUser_And_UserCode(OneName, OneCode, result);
        } else {//如果编译出错，那么棋局结束
            if (errorMessage.substring(0, errorMessage.indexOf("#")).equals(OneName)) {
                errorMessage = "编译错误#" + errorMessage.substring(errorMessage.indexOf("#") + 1);
                SaveSubmitList(OneName, OneCode, "lose", errorMessage, "", TwoName, "");
            } else {
                errorMessage = "运行成功#对方代码编译错误";
                SaveSubmitList(OneName, OneCode, "win", errorMessage, "", TwoName, "");
                saveRankUser_And_UserCode(OneName, OneCode, "yes");
            }

        }
        //销毁本地运行产生的文件夹
        userOne.destroy();
        userTwo.destroy();
    }

    //更新用户排名，更新用户的代码
    @Override
    public void saveRankUser_And_UserCode(String userEmail, String userCode, String result) {
        //根据输赢，更新战绩（平局不影响战绩）
        if (!"".equals(result)) {
            //获取用户排名信息，输赢场数
            QueryWrapper<RankUser> wrapper = new QueryWrapper<>();
            wrapper.eq("user_email", userEmail);
            RankUser rankUser = rankingMapper.selectOne(wrapper);
            Integer win = Integer.valueOf(rankUser.getRankValue().split("/")[0]);//赢的总场数
            Integer lose = Integer.valueOf(rankUser.getRankValue().split("/")[1]);//输的总场数
            if ("yes".equals(result)) {//如果赢了，就让win++
                win++;
            } else {//如果输了，就让lost++（运行超时、运行出错、重复落子、正常输棋都算输）
                lose++;
            }

            //修改用户排名信息
            QueryWrapper<RankUser> updateWrapper = new QueryWrapper<>();
            updateWrapper.eq("user_email", rankUser.getUserEmail());
            rankUser.setRankValue(win + "/" + lose);
            rankUser.setRankFractions(1.0 * Math.round((1.0 * win / (win + lose)) * 100) / 100);
            rankingMapper.update(rankUser, updateWrapper);

            //清除Redis中rank_user表的缓存
            redisUtil.del("gobang_rankUser");
        }

        //更新User表中用户的代码
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("email", userEmail);
        User user = new User();
        user.setEmail(userEmail);
        user.setCodeLanguage(userOne.getLanguage());
        user.setCode(userCode);
        userMapper.update(user, updateWrapper);
    }

    //判断执行结果并存入数据库
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public void JurgeAndSaveSubmitList(String result, String OneCode, String myCheer) {
        String path = userOne.appendPath(RunCodeServiceImpl.basePath, userOne.getUserName());
        path = userOne.appendPath(path, "input.txt");
        String process = FileUtils.read(path);
        String OneName = userOne.getUserName(), TwoName = userTwo.getUserName();
        if ("".equals(result)) {
            result = "draw";//平局
            SaveSubmitList(OneName, OneCode, result, "", process, TwoName, myCheer);
        } else if ("no".equals(result)) {
            result = "lose";
            SaveSubmitList(OneName, OneCode, result, "", process, TwoName, myCheer);
        } else if ("yes".equals(result)) {
            result = "win";
            SaveSubmitList(OneName, OneCode, result, "", process, TwoName, myCheer);
        } else {
            if (result.substring(0, result.indexOf("#")).equals(OneName)) {
                if (result.contains("#timeOut#")) {
                    result = "运行超时#" + result.substring(result.indexOf("#") + 9);
                } else if (result.contains("#repeatedError#")) {
                    result = "运行错误#代码出现重复落子，请查看复盘信息";
                } else {
                    result = "运行错误#" + result.substring(result.indexOf("#") + 1);
                }
                SaveSubmitList(OneName, OneCode, "lose", result, process, TwoName, myCheer);
            } else {
                result = "运行成功#对方代码运行时出现错误";
                SaveSubmitList(OneName, OneCode, "win", result, process, TwoName, myCheer);
            }
        }
    }

    //使棋局运行一个回合
    private String blackFirstRunTool(String firstRun) {
        String errorMessage = "";
        if ("userOne".equals(firstRun)) {//判断谁是黑棋
            Map accept = userOne.doOnceRun();

            if (accept.get("timeOut") != null) {
                errorMessage = userOne.getUserName() + "#timeOut#" + accept.get("timeOut");
            } else if (accept.get("error") != null) {
                errorMessage = userOne.getUserName() + "#" + accept.get("error");
            } else {
                userTwo.setInput((String) accept.get("value"));
                runTime += accept.get("time") + "#";
            }
        } else {
            Map accept = userTwo.doOnceRun();
            if (accept.get("timeOut") != null) {
                errorMessage = userTwo.getUserName() + "#" + accept.get("timeOut");
            } else if (accept.get("error") != null) {
                errorMessage = userTwo.getUserName() + "#" + accept.get("error");
            } else {
                userOne.setInput((String) accept.get("value"));
                runTime += accept.get("time") + "#";
            }
        }
        return errorMessage;
    }


    //运行20回合，返回"yes"代表胜，返回"no"代表输
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public String BlackFirstRun(int index)//index=0是userOne执黑棋，index=1是useTwo执黑棋
    {
        int runTimes = 0;//当tag为1表示黑棋胜，2表示白棋胜
        runTime = "";
        if (index == 0) {
            userOne.setIsMeBlack(1);//1为我是黑棋
            userTwo.setIsMeBlack(-1);//-1为我是白棋
        } else {
            userTwo.setIsMeBlack(1);
            userOne.setIsMeBlack(-1);
        }

        if (index == 1) {//如果对方执黑棋，先调用一步blackFirstRunTool让对方先下
            String result = blackFirstRunTool("userTwo");
            if (result.length() != 0) {
                return result;
            }
        }
        //如果自己执黑棋，调用blackFirstRunTool执行完毕返回结果
        while (runTimes < 20 - index) {
            String result = blackFirstRunTool("userOne");
            if (result.length() != 0) {
                return result;
            }

            //判断是否成局、是否平局、是否重复落子导致输局
            int userOne_result = userOne.check();
            if (userOne_result == 1) {
                return "yes";
            } else if (userOne_result == -1) {//-1代表重复落子
                return userOne.getUserName() + "#repeatedError#";
            }
            if (runTimes + 1 >= 20 - index) {
                return "";
            }

            result = blackFirstRunTool("userTwo");
            if (result.length() != 0) {
                return result;
            }

            //判断对手是否成局、是否平局、是否重复落子导致输局
            int userTwo_result = userTwo.check();
            if (userTwo_result == 1) {//对手成局，那么返回"no"代表自己输了
                return "no";
            } else if (userTwo_result == -1) {//对手重复落子，那么自己赢了
                return "yes";
            }
            runTimes += 2;
        }
        return "";
    }

    //保存单次执行结果，单次执行意思为：用户1白vs用户2黑这一整局
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    @Override
    public void SaveSubmitList(String user, String code, String winner, String error, String process, String fightWith, String myCheer) {
        user = user.replace("-", ".");
        fightWith = fightWith.replace("-", ".");
        Date NowTime = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = format.format(NowTime);
        SubmitList submitList = new SubmitList();
        submitList.setRunTime(runTime);
        submitList.setLanguage(userOne.getLanguage());
        submitList.setEmail(user);
        submitList.setWinner(winner);
        submitList.setFightWith(fightWith);
        submitList.setError(error);
        submitList.setProcess(process);
        submitList.setDate(date);
        submitList.setMyCheer(myCheer);
        submitList.setCode(code);
        submitListMapper.insert(submitList);
    }
}
